// Copyright (c) Microsoft Corporation 
// All rights reserved

namespace Microsoft.Windows.Controls.Specialized.TextModel
{
    using System;
    using System.Diagnostics;

    /// <summary>
    /// Describes the contract for Text Buffer implementations.
    /// </summary>
    public abstract class TextBuffer : ITextBuffer
    {
        #region Private Members

        string _documentType = "text";

        #endregion // Private Members

        #region ITextBuffer Members

        /// <summary>
        /// Gets the starting character index of the specified line
        /// </summary>
        /// <param name="line">
        /// The line number whose starting character index is required
        /// </param>
        public abstract int GetStartOfLine(int line);

        /// <summary>
        /// Gets the length of the specified line (not including the line break characters)
        /// </summary>
        /// <param name="line">
        /// The line number whose length is required
        /// </param>
        public abstract int GetLengthOfLine(int line);

        /// <summary>
        /// Gets the position of the first character in the line containing the character at the specified 
        /// position
        /// </summary>
        public abstract int GetStartOfLineContaining(int position);

        /// <summary>
        /// Gets the position of the last character in the line containing the character at the specified 
        /// position
        /// </summary>
        public abstract int GetEndOfLineContaining(int position);

        /// <summary>
        /// Gets the position of the first character in the line next to the one containing the character
        /// at the specified position
        /// </summary>
        public abstract int GetStartOfNextLine(int position);

        /// <summary>
        /// Gets the position of the first character in the line previous to the one containing the character
        /// at the specified position
        /// </summary>
        public abstract int GetStartOfPreviousLine(int position);

        /// <summary>
        /// Gets the line number that contains the character at the specified position
        /// </summary>
        public abstract int GetLineContaining(int position);

        /// <summary>
        /// Gets the line offset given the absolute position
        /// </summary>
        public abstract int GetColumnOffset(int position);

        /// <summary>
        /// Gets the TextLine given the line number
        /// </summary>
        public abstract BufferLine GetTextLine(int lineNumber);

        /// <summary>
        /// Inserts the given text in the specified position
        /// </summary>
        /// <param name="position">
        /// The point of insertion of text
        /// </param>
        /// <param name="text">
        /// The text to insert
        /// </param>
        public abstract void Insert(int position, string text);

        /// <summary>
        /// Deletes the specified number of characters from the given start position
        /// </summary>
        /// <param name="startPosition">
        /// The position to start deleting characters from
        /// </param>
        /// <param name="charsToDelete">
        /// The number of characters to delete
        /// </param>
        public abstract void Delete(int startPosition, int charsToDelete);

        /// <summary>
        /// Replaces the specified range of characters with the new text.
        /// </summary>
        /// <param name="startPosition">
        /// The starting position of the range of characters to replace
        /// </param>
        /// <param name="charsToReplace">
        /// The number of characters to replace
        /// </param>
        /// <param name="replaceWith">
        /// The new text that replaces the specified range
        /// </param>
        public abstract void Replace(int startPosition, int charsToReplace, string replaceWith);

        /// <summary>
        /// Creates an ITextEdit object that can handle compounds edits on this buffer
        /// </summary>
        public abstract ITextEdit CreateEdit();

        /// <summary>
        /// Freezes the text buffer 
        /// </summary>
        /// <param name="version"></param>
        /// <returns></returns>
        public abstract ITextReader Freeze(ITextVersion version);

        /// <summary>
        /// Indicates whether version belongs to this buffer
        /// </summary>
        public bool VersionBelongsToBuffer(ITextVersion version)
        {
            while (version != this.Version)
            {
                version = version.Next;
                if (version == null)
                {
                    return false;
                }
            }
            return true;
        }

        /// <summary>
        /// Compute length of particular version of this buffer.
        /// </summary>
        /// <param name="version">The version of interest; throws ArgumentException
        /// if !VersionBelongsToBuffer(version)</param>
        public int GetLengthOfVersion(ITextVersion version)
        {
            if (!this.VersionBelongsToBuffer(version))
            {
                throw new ArgumentException("Version does not belong to buffer");
            }
            int delta = 0;

            while (version.Change != null)
            {
                Debug.Assert(version.Next != null);
                delta += version.Change.Delta;
                version = version.Next;
            }

            // Now, apply the delta to the current length
            return this.Length - delta;
        }
        
        /// <summary>
        /// A buffer changed notification event
        /// </summary>
        public abstract event TextChangedEventHandler Changed;

        #endregion // ITextBuffer Members

        #region ITextReader Members

        /// <summary>
        /// Gets the specified length of text starting from the given index
        /// </summary>
        /// <param name="startIndex">
        /// The starting index to get the text from
        /// </param>
        /// <param name="length">
        /// The length of text to get
        /// </param>
        public abstract string GetText(int startIndex, int length);

        /// <summary>
        /// Gets text from the specified start index to the end of the text store
        /// </summary>
        /// <param name="startIndex">
        /// The starting index of the text to get
        /// </param>
        public abstract string GetText(int startIndex);

        /// <summary>
        /// Get a character from the specified index
        /// </summary>
        /// <param name="index">
        /// The index of the character to get
        /// </param>
        public abstract char GetChar(int index);

        /// <summary>
        /// Gets the entire text from the underlying text store
        /// </summary>
        public abstract string GetText();

        /// <summary>
        /// Converts a range of text to a character array
        /// </summary>
        /// <param name="startIndex">
        /// The starting index of the range of text
        /// </param>
        /// <param name="length">
        /// The length of the text
        /// </param>
        public abstract char[] ToCharArray(int startIndex, int length);

        /// <summary>
        /// Converts the entire text into a character array
        /// </summary>
        public abstract char[] ToCharArray();

        /// <summary>
        /// Copies a range of text to a destination character array.
        /// </summary>
        /// <param name="sourceIndex">
        /// The starting index to copy from
        /// </param>
        /// <param name="destination">
        /// The destination array
        /// </param>
        /// <param name="destinationIndex">
        /// The index into the array to start copying to
        /// </param>
        /// <param name="count">
        /// The number of characters to copy
        /// </param>
        public abstract void CopyTo(int sourceIndex, char[] destination, int destinationIndex, int count);

        /// <summary>
        /// Gets the document type for the text this buffer contains
        /// </summary>
        public virtual string DocumentType
        {
            get
            {
                return _documentType;
            }

            set
            {
                // Validation
                if (value == null)
                    throw new ArgumentNullException("DocumentType cannot be null");

                _documentType = value;
            }
        }

        /// <summary>
        /// Gets the length of the text in the backing store
        /// </summary>
        public abstract int Length
        {
            get;
        }

        /// <summary>
        /// Gets the number of lines in the backing store
        /// </summary>
        public abstract int LineCount
        {
            get;
        }

        /// <summary>
        /// Gets the current version of the buffer
        /// </summary>
        public abstract ITextVersion Version
        {
            get;
        }        

        #endregion // ITextReader Members

        #region Version Utilities

        public static int CompareVersions(ITextVersion version1, ITextVersion version2)
        {
            // We walk both the text versions forward comparing them against each other's initial reference
            // at each point.  Whichever one hits the match first is the one that came earlier.  Or in case
            // one hits null before the other, that one came later.
            ITextVersion reference1 = version1, reference2 = version2;

            // Check for equality
            if (version1 == version2)
            {
                return 0;
            }

            // Check for null
            if (version1 == null)
                return 1;
            else if (version2 == null)
                return -1;

            while (true)
            {
                version1 = version1.Next;
                version2 = version2.Next;

                if (version1 == null || version2 == reference1)
                {
                    return +1;
                }

                if (version1 == reference2 || version2 == null)
                {
                    return -1;
                }

                // Detect circularities
                if (version1 == reference1 || version2 == reference2)
                {
                    throw new InvalidOperationException("Circularity detected in text buffer version.");
                }
            }
        }
        #endregion

        public abstract object this[object key] { get; set; }
    }
}
